# A simple quiz program with a configuration file for defining the questions as well as some localization strings
# No Database, No XML, No installation, just configure the config file, set IP and PORT below and and run: python SimpleQuiz.py
# Author: teuvoe < at > gmail dot com

import cherrypy
import os


# N O T E !!!  SET AT LEAST PORT AND IP BELOW

SEP = "::"                           # separates a key from a value in a config file
HTTP_SERVER_PORT  = 8080             # HTTP port for the query
HTTP_SERVER_HOST  = "127.0.0.1"      # IP where the HTTP server binds
QUERY_FILE        = "Questions.txt"  # location and the file name where the config/questions are
SPACE_CNT         = 50               # spaces between answer and verdict
FIELDSET_WIDTH    = 35               # percentage for fieldset html tag


HTTP_MAIN = """http://%s:%s/""" % (HTTP_SERVER_HOST, HTTP_SERVER_PORT)
#debug = True
debug = False

class QuestionParser:
  """
  Parses the question file
  """
  
  def __init__(self):
    global debug
    self.queryFile = QUERY_FILE      # Config file containing the following configurations:
    self.queryQuestions = []         # Question related configurations, answer choices and correct answer.  E.g:  self.queryQuestions[<question nbr>] = {"Q" : "What is xyz?", "A": aso...}
    self.queryConfig = {}            # All Other configurations.   E.g: self.queryConfig["Title"] = "Some title here"
    self.parseQuestions()
    
    
  def parseQuestions(self):
    """
    Parse the config file and store data to internal data structures
    Note! it's important to remain the config file structure as is
    """
    inQuestions = False
    questionNbr = -1
    
    for line in open(self.queryFile):
      if not line.startswith("#") and not len(line) < 3:
        lineFirstWord = line.split()[0]
        if lineFirstWord == "Q"+SEP:
          inQuestions = True
          questionNbr += 1
        if not inQuestions:
            self.queryConfig[line.split(SEP)[0]] = line.split(SEP)[1].strip()
        else:
          if lineFirstWord.startswith("Q"+SEP):
            self.queryQuestions.append( {"Q" : line.split(SEP)[1].strip()} )
          else:
            self.queryQuestions[questionNbr][lineFirstWord.rstrip(SEP)] = line.split(SEP)[1].strip()
    self.questionCount = questionNbr + 1
    
    if debug:
      print "\nQueryConfig:", self.queryConfig
      print "\nQueryQuestions:", self.queryQuestions
  
  
  def getConfigParam(self, paramName):
    """
    Return other than single question related configuration parameter value by it's name
    """
    return self.queryConfig[paramName]
    
    
  def getQuestionCount(self):
    """
    Return total amount of questions in config file
    """
    return self.questionCount
    
    
  def getQuestionChoice(self, questionNbr, choiceNbr):
    """
    Get single answer choise (for a question) by it's number
    """
    return self.queryQuestions[questionNbr]["C" + str(choiceNbr+1)]
  
  
  def getQuestionChoiceCount(self, questionNbr):
    """
    Return total count of answer choises for a question
    """
    return len(self.queryQuestions[questionNbr].keys()) - 3   # 3= Q: E: and A: lines not included in the count
    
    
  def getQuestionParam(self, questionNbr, paramName):
    """
    Return question related configuration parameter value by it's name
    """
    return self.queryQuestions[questionNbr][paramName]

    
qp = QuestionParser() # a global object


class Form:
  """
  Provide the quiz HTML form based on the config file
  """
  
  def formRadioButtons(self):
    """
    Make explanation + radio buttons for the questions
    """
    ret = ""
    radioString = """
            <fieldset style="width:%d%%">
            <legend> &nbsp;  %s &nbsp; </legend> <br />
            %s
            </fieldset>
            <br /><br />"""
    for i in range(qp.getQuestionCount()):
      tmp = ""
      explanation = qp.getQuestionParam(i, "E")         # make explanation for the question, can be 'empty' (len < 3)
      if not len(explanation) < 3:
        tmp = "\n" +  explanation + "<br><br>\n"
        
      for b in range(qp.getQuestionChoiceCount(i)):     # make radio buttons i.e. answer choises for the question
        tmp += """<input type="radio" name="Q{0}" value="{1}" /> {1}<br />\n""".format( (i+1), qp.getQuestionChoice(i, b) )
      ret += radioString % (FIELDSET_WIDTH, qp.getQuestionParam(i, "Q"), tmp)
    return ret


  def getUserInfo(self):
    return """
        <form action="%sCheckAnswers/" method="post">
        <fieldset style="width:%d%%">
        <legend> &nbsp; %s &nbsp; </legend>
        <br>
        %s <input type="text" name="name" value="" size="35" maxlength="45"/>
        <br><br>
        %s <input type="text" name="id" value="" size="35" maxlength="45"/>
        <br><br>
        </fieldset>
        <br><br>
    """ % (HTTP_MAIN, FIELDSET_WIDTH, qp.getConfigParam("Identify"), qp.getConfigParam("Give_name"), qp.getConfigParam("Give_email"))
    
    
  def index(self):
    """
    HTML form construction starts here
    """
    ret = "<p><b><h1>%s</h1></b><br></p>" % qp.getConfigParam("Title")
    ret += self.getUserInfo()
    ret += """\n
            %s <br />
            <p><input type="submit" value="%s"/></p>
            <p><input type="reset" value="%s"/></p>
            </form><br>""" % (self.formRadioButtons(), qp.getConfigParam("SendButton"), qp.getConfigParam("ClearButton"))
    return ret
  index.exposed = True
  
    
class CheckAnswers(object):
  """
  Check the answers based on the config file and color code (green/red) the result lines accordingly
  """
  
  def green(self, string):
    return """<span style="color:green; font-family:courier">""" + string + "</span>"


  def red(self, string):
    return """<span style="color:red; font-family:courier" >""" + string + "</span>"
  

  def reserveSpace(self, string):
    return string + ((SPACE_CNT - len(string)) * "&nbsp;")


  def getQuizAnalyzation(self, dictAnswers):
    """
    Analyze answers got against the correct answer stated in the config file
    """
    if debug: print dictAnswers
    ret = ""
    answers = []
    correctCount = 0

    # Form values come in dictionary via HTTP GET -> need to make questions in order:
    for key in sorted(dictAnswers.keys()):
      answers.append(dictAnswers[key])
      
    for nbr, answer in enumerate(answers):
      if debug: print nbr, answer
      if not answer == None and not answer == dictAnswers["id"] and not answer == dictAnswers["name"]:  # also name and id/email sent IN GET, don't treat these as answers to questions
        correct = qp.getQuestionParam(nbr, qp.getQuestionParam(nbr, "A"))
        if str(answer) == str(correct):
          ret += self.green("\n<br>" + self.reserveSpace(qp.getQuestionParam(nbr, "Q")) + "'%s' was correct answer" %  correct)  
          correctCount += 1
        else:
          if answer == None:
            ret += self.red("\n<br>" + self.reserveSpace(qp.getQuestionParam(nbr, "Q")) + " Not answered - Correct: '%s'" % correct)
          else:
            ret += self.red("\n<br>" + self.reserveSpace(qp.getQuestionParam(nbr, "Q")) + "'%s' was wrong answer." % str(answer) + " Correct: '%s'" % correct) 
    return ret, correctCount


  def storeScore(self, correctCount, questionCount, dicArgs):
    """
    Save the result score to a file named after the person who did the query
    Only one save/answer is allowed with that name.
    The time of answer can be seen from the file creation time.
    """
    if str(dicArgs["name"]) == "" or str(dicArgs["id"]) == "":
      return False, "No name or id given"
    else:
      if os.path.isfile(dicArgs["name"]+"_score.log"):
        return False, "User has answered already"
      try:
        fp = open(dicArgs["name"]+"_score.log", "w")
      except:
        return False, "Error in opening file '%s'" % dicArgs["name"]+"_score.log"
      
      fp.write(dicArgs["name"] + " : " + str(correctCount) + "/" + str(questionCount) + "    " + dicArgs["id"])
      fp.close()
      return True, "all ok, query results stored"
      
    
  def index(self, *args, **kwargs): 
    """
    Check answers start here
    """
    global debug
    if debug:
      print "kwargs is: " , kwargs, "\n\n"
    ret = "<H1>%s</H1>"% qp.getConfigParam("Title") + "\n"
    ret += qp.getConfigParam("ThankYou") + "<br><br>"
    tmp, correctCount = self.getQuizAnalyzation(kwargs)
    ret += tmp
    ret += "\n<br><br><br>" + qp.getConfigParam("YourScore") % (correctCount, qp.getQuestionCount())
    success, errText = self.storeScore(correctCount, qp.getQuestionCount(), kwargs)
    if success:
      return ret
    else:
      print "Error was: " + errText 
      return qp.getConfigParam("ErrorNoUserInfo")
  index.exposed = True
  
  
class Start:
  """
  Web server/Quiz start page, provide link to query
  No need to use this view if so wanted -> use the query page directly
  """
  CheckAnswers = CheckAnswers()
  Form = Form()
  
  def index(self):
    ret = "<H1>%s</H1>"% qp.getConfigParam("Title") + "\n"
    ret += "<br><b>%s</b><br><br>\n" % qp.getConfigParam("Greeting")
    ret += "%s<br>\n" % qp.getConfigParam("Info")
    ret += """<br><a href="%sForm">Take the quiz!</a>""" % HTTP_MAIN
    return ret
  index.exposed = True


cherrypy.quickstart(Start()) 

